<!--
 * Copyright 2009, Google Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-->
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">
<title>WebGL FishTank</title>
<link href="../shared/base.css" rel="stylesheet" />
<script src="../khronos/webgl-debug.js"></script>
<script src="../tdl/base.js"></script>
<script src="../shared/utils.js"></script>
<script>
tdl.require('tdl.buffers');
tdl.require('tdl.fast');
tdl.require('tdl.fps');
tdl.require('tdl.log');
tdl.require('tdl.math');
tdl.require('tdl.models');
tdl.require('tdl.primitives');
tdl.require('tdl.programs');
tdl.require('tdl.textures');
tdl.require('tdl.webgl');
window.onload = main;

// globals
var gl;                   // the gl context.
var canvas;               // the canvas
var math;                 // the math lib.
var fast;                 // the fast math lib.
var g_fpsTimer;           // object to measure frames per second;
var g_logGLCalls = true   // whether or not to log webgl calls
var g_debug = false;      // whether or not to debug.
var g_drawOnce = false;
var g_setSettingElements = [];
var g_sharkWorldMats = [];
var g_beamWorldMats = [];
var g_refract = true;
var g_requestId;

//g_refract = false;
//g_debug = true;
//g_drawOnce = true;

var g_numFish          = 1;
var g_numSharks        = 0;
var g_eyeSpeed         = 0.01;
var g_eyeHeight        = 8;
var g_eyeRadius        = 60;
var g_tailSpeed        = 30;
var g_tailOffsetMult   = 1;
var g_endOfDome        = Math.PI / 8;
var g_tankRadius       = 22;
var g_tankHeight       = 30;
var g_tankRefractionRatio = 1.33;
var g_standHeight      = 25;
var g_fishSpeed        = 0.3;
var g_fishClockOffset  = 0.3;
var g_fishXClock       = 1;
var g_fishYClock       = 0.17;
var g_fishZClock       = 1.09;
var g_sharkSpeed       = 0.3;
var g_sharkClockOffset = 17;
var g_sharkXClock      = 1;
var g_sharkYClock      = 0.17;
var g_sharkZClock      = 1;

var g_skyBoxUrls = [
  'assets/lobby/right.jpg',
  'assets/lobby/left.jpg',
  'assets/lobby/up.jpg',
  'assets/lobby/down.jpg',
  'assets/lobby/front.jpg',
  'assets/lobby/back.jpg']

function ValidateNoneOfTheArgsAreUndefined(functionName, args) {
  for (var ii = 0; ii < args.length; ++ii) {
    if (args[ii] === undefined) {
      tdl.error("undefined passed to gl." + functionName + "(" +
                tdl.webgl.glFunctionArgsToString(functionName, args) + ")");
    }
  }
}

function Log(msg) {
  if (g_logGLCalls) {
    tdl.log(msg);
  }
}

function LogGLCall(functionName, args) {
  if (g_logGLCalls) {
    ValidateNoneOfTheArgsAreUndefined(functionName, args)
    tdl.log("gl." + functionName + "(" +
            tdl.webgl.glFunctionArgsToString(functionName, args) + ")");
  }
}

/**
 * Set Fish
 */
function setupFish() {
  var textures = {
    diffuseSampler: tdl.textures.loadTexture('assets/rock-color.png'),
    bumpSampler: tdl.textures.loadTexture('assets/rock-nmap.png'),
    skybox: tdl.textures.loadTexture(g_skyBoxUrls)};
  var program = tdl.programs.loadProgramFromScriptTags(
      'fishVertexShader',
      'fishFragmentShader');
  var sphereArrays = tdl.primitives.createSphere(1, 10, 10);
  tdl.primitives.addTangentsAndBinormals(sphereArrays);
  tdl.primitives.reorient(sphereArrays,
      [0.2, 0, 0, 0,
       0, 0.5, 0, 0,
       0, 0, 1, 0,
       0, 0, 0, 1]);
  var tailArrays = tdl.primitives.createCresent(0.75, 1, 0.5, 0.01, 10);
  tdl.primitives.addTangentsAndBinormals(tailArrays);
  tdl.primitives.reorient(tailArrays,
      [-1, 0, 0, 0,
       0, 1, 0, 0,
       0, 0, -1, 0,
       0, 0, 1.7, 1]);
  var finArrays = tdl.primitives.createCresent(1, 1, 0.5, 0.01, 10);
  tdl.primitives.addTangentsAndBinormals(finArrays);
  tdl.primitives.reorient(finArrays,
      [-1, 0, 0, 0,
       0, 1, 0, 0,
       0, 0, -1, 0,
       0, 0, 0.7, 1]);
  var arrays = tdl.primitives.concat([sphereArrays, tailArrays, finArrays]);
  return new tdl.models.Model(program, arrays, textures);
}

/**
 * Setup Shark
 */
function setupShark() {
  var textures = {
      diffuseSampler: tdl.textures.loadTexture('assets/shark.png')};
  textures.diffuseSampler.setParameter(gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
  var program = tdl.programs.loadProgramFromScriptTags(
      'sharkVertexShader',
      'sharkFragmentShader');
  var sphereArrays = tdl.primitives.createSphere(1, 10, 10);
  tdl.primitives.reorient(sphereArrays,
      [0.2, 0, 0, 0,
       0, 0.2, 0, 0,
       0, 0, 1, 0,
       0, 0, 0, 1]);
  var tailArrays = tdl.primitives.createCresent(0.3, 0.5, 0.3, 0.01, 10);
  tdl.primitives.reorient(tailArrays,
      [-1, 0, 0, 0,
       0, 1, 0, 0,
       0, 0, -1, 0,
       0, 0, 1.3, 1]);
  var topFinArrays = tdl.primitives.createCresent(0.5, 0.7, 0.2, 0.01, 10, 0, 0.5);
  tdl.primitives.reorient(topFinArrays,
      [-1, 0, 0, 0,
       0, 1, 0, 0,
       0, 0, -1, 0,
       0, 0, 0.4, 1]);
  var leftFinArrays = tdl.primitives.clone(topFinArrays);
  tdl.primitives.reorient(leftFinArrays,
      math.matrix4.mul(
        math.matrix4.translation([0, 0, -0.2]),
        math.matrix4.rotationZ(math.degToRad(120))));
  var rightFinArrays = tdl.primitives.clone(topFinArrays);
  tdl.primitives.reorient(rightFinArrays,
      math.matrix4.mul(
        math.matrix4.translation([0, 0, -0.2]),
        math.matrix4.rotationZ(math.degToRad(-120))));
  var arrays = tdl.primitives.concat([
      sphereArrays,
      tailArrays,
      topFinArrays,
      leftFinArrays,
      rightFinArrays]);
  return new tdl.models.Model(program, arrays, textures);
}

/**
 * Setup Laser
 */
function setupLaser() {
  var textures = {
      colorMap: tdl.textures.loadTexture('assets/beam.png')};
  var program = tdl.programs.loadProgramFromScriptTags(
      'laserVertexShader',
      'laserFragmentShader');
  var beam1Arrays = tdl.primitives.createPlane(1, 1, 1, 1);
  delete beam1Arrays.normal;
  tdl.primitives.reorient(beam1Arrays,
        math.matrix4.translation([0, 0, -0.5]));
  var beam2Arrays = tdl.primitives.clone(beam1Arrays);
  var beam3Arrays = tdl.primitives.clone(beam1Arrays);
  tdl.primitives.reorient(beam2Arrays,
        math.matrix4.rotationZ(math.degToRad(120)));
  tdl.primitives.reorient(beam3Arrays,
        math.matrix4.rotationZ(math.degToRad(-120)));
  var arrays = tdl.primitives.concat([
      beam1Arrays,
      beam2Arrays,
      beam3Arrays]);
  return new tdl.models.Model(program, arrays, textures);
}

/**
 * Sets up the Skybox
 */
function setupSkybox() {
  var textures = {
    skybox: tdl.textures.loadTexture(g_skyBoxUrls)};
  var program;
  if (g_refract) {
    program = tdl.programs.loadProgramFromScriptTags(
        'refractSkyboxVertexShader',
        'refractSkyboxFragmentShader');
    if (!program) {
      g_refract = false;
    }
  }

  if (!g_refract) {
      program = tdl.programs.loadProgramFromScriptTags(
          'skyboxVertexShader',
          'skyboxFragmentShader');
  }
  var arrays = tdl.primitives.createPlane(2, 2, 1, 1);
  delete arrays['normal'];
  delete arrays['texCoord'];
  tdl.primitives.reorient(arrays,
      [1, 0, 0, 0,
       0, 0, 1, 0,
       0,-1, 0, 0,
       0, 0, 0.99, 1]);
  return new tdl.models.Model(program, arrays, textures);
}

/**
 * Sets up the Sand
 */
function setupSand() {
  var textures = {
    diffuseSampler: tdl.textures.loadTexture('assets/sand-color.png'),
    bumpSampler: tdl.textures.loadTexture('assets/sand-nmap.png')};
  var program = tdl.programs.loadProgramFromScriptTags(
      'normalMapVertexShader',
      'normalMapFragmentShader');
  // expand radius at end of dome matches desired radius.
  var radius = g_tankRadius / Math.sin(g_endOfDome);
  var arrays = tdl.primitives.createSphere(radius, 30, 5, 0, g_endOfDome);
  tdl.primitives.applyPlanarUVMapping(arrays.position, arrays.texCoord);
  tdl.primitives.addTangentsAndBinormals(arrays);
  tdl.primitives.reorient(arrays,
      [1, 0, 0, 0,
       0, 1, 0, 0,
       0, 0, 1, 0,
       0, -radius, 0, 1]);
  return new tdl.models.Model(program, arrays, textures);
}

/**
 * Sets up the Glass
 */
function setupGlass() {
  var textures = {
    bumpSampler: tdl.textures.loadTexture('assets/sand-nmap.png'),
    skybox: tdl.textures.loadTexture(g_skyBoxUrls)};
  var program = tdl.programs.loadProgramFromScriptTags(
      'glassVertexShader',
      'glassFragmentShader');
  var arrays = tdl.primitives.createCylinder(
      g_tankRadius, g_tankHeight, 30, 1, false, false);
  tdl.primitives.addTangentsAndBinormals(arrays);
  tdl.primitives.reorient(arrays,
      [1, 0, 0, 0,
       0, 1, 0, 0,
       0, 0, 1, 0,
       0, (g_tankHeight - 6) / 2, 0, 1]);
  return new tdl.models.Model(program, arrays, textures);
}

/**
 * Sets up the Stand
 */
function setupStand() {
  var textures = {
    diffuseSampler: tdl.textures.loadTexture('assets/stand-color.jpg'),
    bumpSampler: tdl.textures.loadTexture([128,128,255,255])};
  var program = tdl.programs.loadProgramFromScriptTags(
      'normalMapVertexShader',
      'normalMapFragmentShader');
  var arrays = tdl.primitives.createCylinder(
      g_tankRadius + 0.2, g_standHeight, 30, 1, false, false);
  tdl.primitives.addTangentsAndBinormals(arrays);
  var domeHeight = g_tankRadius * (1 - Math.cos(g_endOfDome));
  tdl.primitives.reorient(arrays,
      [1, 0, 0, 0,
       0, 1, 0, 0,
       0, 0, 1, 0,
       0, (g_standHeight * -0.5) - domeHeight - 1, 0, 1]);
  tdl.primitives.mulComponents(arrays.texCoord, [30, g_standHeight]);
  return new tdl.models.Model(program, arrays, textures);
}

/**
 * Sets the count
 */
function setSetting(elem, id) {
  switch (id) {
  case 0:
  case 1:
  case 2:
  case 3:
    g_numFish = Math.pow(10, id);
    for (var ii = 0; ii < 4; ++ii) {
      g_setSettingElements[ii].style.color = "gray";
    }
    elem.style.color = "red";
    break;
  case 4:
    if (g_numSharks) {
      g_numSharks = 0;
      elem.style.color = "gray";
    } else {
      g_numSharks = 3;
      elem.style.color = "red";
    }
    break;
  }
}

/**
 * Sets up the count buttons.
 */
function setupCountButtons() {
  for (var ii = 0; ii < 100; ++ii) {
    var elem = document.getElementById("setSetting" + ii);
    if (!elem) {
      break;
    }
    g_setSettingElements.push(elem);
    elem.onclick = function(elem, id) {
      return function () {
        setSetting(elem, id);
      }}(elem, ii);
  }
  setSetting(document.getElementById("setSetting0"), 0);
}

function main() {
  setupFullscreen("fullscreen", "body");

  math = tdl.math;
  fast = tdl.fast;
  canvas = document.getElementById("canvas");

  setupCountButtons();

  g_fpsTimer = new tdl.fps.FPSTimer();

  //canvas = WebGLDebugUtils.makeLostContextSimulatingCanvas(canvas);
  // tell the simulator when to lose context.
  //canvas.loseContextInNCalls(100000);

  tdl.webgl.registerContextLostHandler(canvas, handleContextLost);
  tdl.webgl.registerContextRestoredHandler(canvas, handleContextRestored);

  gl = tdl.webgl.setupWebGL(canvas);
  if (!gl) {
    return false;
  }
  if (g_debug) {
    gl = tdl.webgl.makeDebugContext(gl, undefined, LogGLCall);
  }

  initialize();
}

function handleContextLost() {
  tdl.log("context lost");
  cancelAnimationFrame(g_requestId);
}

function handleContextRestored() {
  tdl.log("context restored");
  initialize();
}

/**
 * Initializes stuff.
 */
function initialize() {
  gl.enable(gl.DEPTH_TEST);
  gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
//  gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, gl.TRUE);

  Log("--Setup Skybox---------------------------------------");
  var skybox = setupSkybox();
  Log("--Setup Fish-----------------------------------------");
  var fish = setupFish();
  Log("--Setup Shark----------------------------------------");
  var shark = setupShark();
  Log("--Setup Laser----------------------------------------");
  var laser = setupLaser();
  Log("--Setup Sand-----------------------------------------");
  var sand = setupSand();
  Log("--Setup Glass----------------------------------------");
  var glass = setupGlass();
  Log("--Setup Stand----------------------------------------");
  var stand = setupStand();

  var then = 0.0;
  var clock = 0.0;
  var fpsElem = document.getElementById("fps");

  var projection = new Float32Array(16);
  var view = new Float32Array(16);
  var world = new Float32Array(16);
  var worldInverse = new Float32Array(16);
  var worldInverseTranspose = new Float32Array(16);
  var viewProjection = new Float32Array(16);
  var worldViewProjection = new Float32Array(16);
  var viewInverse = new Float32Array(16);
  var viewProjectionInverse = new Float32Array(16);
  var viewDirectionProjectionInverse = new Float32Array(16);
  var eyePosition = new Float32Array(3);
  var target = new Float32Array(3);
  var up = new Float32Array([0,1,0]);
  var lightWorldPos = new Float32Array(3);
  var v3t0 = new Float32Array(3);
  var v3t1 = new Float32Array(3);
  var m4t0 = new Float32Array(16);
  var m4t1 = new Float32Array(16);
  var m4t2 = new Float32Array(16);
  var m4t3 = new Float32Array(16);
  var zero4 = new Float32Array(4);
  var one4 = new Float32Array([1,1,1,1]);
  var colorMult = new Float32Array([1,1,1,1]);

  // Sky uniforms.
  var skyConst = {
      viewDirectionProjectionInverse: viewDirectionProjectionInverse};
  if (g_refract) {
    skyConst = {
      viewProjectionInverse: viewProjectionInverse,
      viewInverse: viewInverse,
      tankRadiusSquared: g_tankRadius * g_tankRadius,
      tankRadiusInverse: 1.0 / g_tankRadius,
      tankRefractionRatio: g_tankRefractionRatio
    };
  }
  var skyPer = {};

  // Sand uniforms.
  var sandConst = {
    viewInverse: viewInverse,
    lightWorldPos: lightWorldPos,
    lightColor: one4,
    emissive: zero4,
    ambient: zero4,
    specular: one4,
    shininess: 5,
    specularFactor: 0.3};
  var sandPer = {
    world: world,
    worldViewProjection: worldViewProjection,
    worldInverse: worldInverse,
    worldInverseTranspose: worldInverseTranspose};

  // Fish uniforms
  var fishConst = {
    viewInverse: viewInverse,
    lightWorldPos: lightWorldPos,
    lightColor: one4,
    emissive: zero4,
    ambient: zero4,
    specular: one4,
    shininess: 100,
    specularFactor: 1.0};
  var fishPer = {
    world: world,
    worldViewProjection: worldViewProjection,
    worldInverse: worldInverse,
    worldInverseTranspose: worldInverseTranspose,
    colorMult: colorMult,
    time: 0};

  // Shark uniforms
  var sharkConst = {
    viewInverse: viewInverse,
    lightWorldPos: lightWorldPos,
    lightColor: one4,
    ambient: zero4,
    specular: one4,
    shininess: 10,
    specularFactor: 0.3};
  var sharkPer = {
    world: world,
    worldViewProjection: worldViewProjection,
    worldInverse: worldInverse,
    worldInverseTranspose: worldInverseTranspose,
    time: 0};

  // Laser uniforms
  var laserConst = {};
  var laserPer = {
    worldViewProjection: worldViewProjection};

  // Stand uniforms.
  var standConst = {
    viewInverse: viewInverse,
    lightWorldPos: lightWorldPos,
    lightColor: one4,
    emissive: zero4,
    ambient: zero4,
    specular: one4,
    shininess: 5,
    specularFactor: 0.3};
  var standPer = {
    world: world,
    worldViewProjection: worldViewProjection,
    worldInverse: worldInverse,
    worldInverseTranspose: worldInverseTranspose};

  // Glass uniforms.
  var glassConst = {
    viewInverse: viewInverse,
    world: world,
    worldViewProjection: worldViewProjection,
    worldInverse: worldInverse,
    worldInverseTranspose: worldInverseTranspose};
  var glassBackPer = {
        normMult: -1};
  var glassFrontPer = {
        normMult: -1};

  var frameCount = 0;

  function render() {
    var now = (new Date()).getTime() * 0.001;
    var elapsedTime;
    if(then == 0.0) {
      elapsedTime = 0.0;
    } else {
      elapsedTime = now - then;
    }
    then = now;

    frameCount++;

    g_fpsTimer.update(elapsedTime);
    fpsElem.innerHTML = g_fpsTimer.averageFPS;

    clock += elapsedTime;
    eyePosition[0] = Math.sin(clock * g_eyeSpeed) * g_eyeRadius;
    eyePosition[1] = g_eyeHeight;
    eyePosition[2] = Math.cos(clock * g_eyeSpeed) * g_eyeRadius;

    gl.colorMask(true, true, true, true);
    gl.clearColor(Math.random(),0,0,0);
    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT | gl.STENCIL_BUFFER_BIT);

    fast.matrix4.perspective(
        projection,
        math.degToRad(30),
        canvas.clientWidth / canvas.clientHeight,
        1,
        5000);
    fast.matrix4.lookAt(
        view,
        eyePosition,
        target,
        up);
    fast.matrix4.mul(viewProjection, view, projection);
    fast.matrix4.inverse(viewInverse, view);
    fast.matrix4.inverse(viewProjectionInverse, viewProjection);
    fast.matrix4.copy(m4t0, view);
    fast.matrix4.setTranslation(m4t0, [0, 0, 0]);
    fast.matrix4.mul(m4t1, m4t0, projection);
    fast.matrix4.inverse(viewDirectionProjectionInverse, m4t1);

    fast.matrix4.getAxis(v3t0, viewInverse, 0); // x
    fast.matrix4.getAxis(v3t1, viewInverse, 1); // y;
    fast.mulScalarVector(v3t0, 20, v3t0);
    fast.mulScalarVector(v3t1, 30, v3t1);
    fast.addVector(lightWorldPos, eyePosition, v3t0);
    fast.addVector(lightWorldPos, lightWorldPos, v3t1);

//      view: view,
//      projection: projection,
//      viewProjection: viewProjection,

    gl.disable(gl.BLEND);
    gl.enable(gl.CULL_FACE);

    math.resetPseudoRandom();
    var pseudoRandom = math.pseudoRandom;
    //var pseudoRandom = function() {
    //  return 0.5;
    //};

    // Draw Skybox
    Log("--Draw Sky---------------------------------------");
    skybox.drawPrep(skyConst);
    skybox.draw(skyPer);

    // Draw Sand
    Log("--Draw Sand---------------------------------------");
    sand.drawPrep(sandConst);
    fast.matrix4.translation(world, [0, -5, 0]);
    fast.matrix4.mul(worldViewProjection, world, viewProjection);
    fast.matrix4.inverse(worldInverse, world);
    fast.matrix4.transpose(worldInverseTranspose, worldInverse);
    sand.draw(sandPer);

    // Draw Stand
    Log("--Draw Stand---------------------------------------");
    gl.disable(gl.CULL_FACE);
    stand.drawPrep(standConst);
    fast.matrix4.translation(world, [0, -5, 0]);
    fast.matrix4.mul(worldViewProjection, world, viewProjection);
    fast.matrix4.inverse(worldInverse, world);
    fast.matrix4.transpose(worldInverseTranspose, worldInverse);
    stand.draw(standPer);

    gl.enable(gl.CULL_FACE);

    // Draw Sharks.
    Log("--Draw Sharks---------------------------------------");

    shark.drawPrep(sharkConst);
    for (var ii = 0; ii < g_numSharks; ++ii) {
      if (!g_sharkWorldMats[ii]) {
        g_sharkWorldMats[ii] = new Float32Array(16);
        g_beamWorldMats[ii] = new Float32Array(16);
      }
      var sharkClock = clock * g_sharkSpeed + ii * g_sharkClockOffset;
      var speed = 1;
      var xRadius = g_tankRadius - 6.0;
      var yRadius = 4.0;
      var zRadius = xRadius;
      var xClock = sharkClock * speed * g_sharkXClock;
      var yClock = sharkClock * speed * g_sharkYClock;
      var zClock = sharkClock * speed * g_sharkZClock;
      var x = Math.sin(xClock) * xRadius;
      var y = Math.sin(yClock) * yRadius;
      var z = Math.cos(zClock) * zRadius;
      var nextX = Math.sin(xClock + 0.01) * xRadius;
      var nextY = Math.sin(yClock + 0.01) * yRadius;
      var nextZ = Math.cos(zClock + 0.01) * zRadius;
      fast.matrix4.mul(g_sharkWorldMats[ii],
          fast.matrix4.scaling(m4t0, [6, 6, 6]),
          fast.matrix4.cameraLookAt(m4t1, [x, y, z], [nextX, nextY, nextZ], [0, 1, 0]));
      fast.matrix4.copy(world, g_sharkWorldMats[ii]);
      fast.matrix4.mul(worldViewProjection, world, viewProjection);
      fast.matrix4.inverse(worldInverse, world);
      fast.matrix4.transpose(worldInverseTranspose, worldInverse);
      sharkPer.time = (clock + ii * g_sharkClockOffset) * 3;
      shark.draw(sharkPer);
    }

    // Draw Fishes.
    Log("--Draw Fish---------------------------------------");

    fish.drawPrep(fishConst);
    var fishClock = clock * g_fishSpeed;
    for (var ii = 0; ii < g_numFish; ++ii) {
      var fishClock = clock * g_fishSpeed + ii * g_fishClockOffset;
      var speed = 0.5 + math.pseudoRandom() * 4;
      var scale = 1.0 + math.pseudoRandom() * 1;
      var xRadius = 2.0 + pseudoRandom() * (g_tankRadius - 8.0);
      var yRadius = 2.0 + pseudoRandom() * (g_tankHeight - 8.0) * 0.3;
      var zRadius = 2.0 + pseudoRandom() * (g_tankRadius - 8.0);
      var xClock = fishClock * speed * g_fishXClock;
      var yClock = fishClock * speed * g_fishYClock;
      var zClock = fishClock * speed * g_fishZClock;
      var x = Math.sin(xClock) * xRadius;
      var y = Math.sin(yClock) * yRadius + 4;
      var z = Math.cos(zClock) * zRadius;
      var nextX = Math.sin(xClock + 0.01) * xRadius;
      var nextY = Math.sin(yClock + 0.01) * yRadius + 4;
      var nextZ = Math.cos(zClock + 0.01) * zRadius;
      colorMult[0] = 0.7 + math.pseudoRandom() * 0.3;
      colorMult[1] = 0.7 + math.pseudoRandom() * 0.3;
      colorMult[2] = 0.7 + math.pseudoRandom() * 0.3;

      fast.matrix4.mul(world,
          fast.matrix4.scaling(m4t0, [scale, scale, scale]),
          fast.matrix4.cameraLookAt(m4t1, [x, y, z], [nextX, nextY, nextZ], [0, 1, 0]));
      fast.matrix4.mul(worldViewProjection, world, viewProjection);
      fast.matrix4.inverse(worldInverse, world);
      fast.matrix4.transpose(worldInverseTranspose, worldInverse);
      fishPer.time = (clock + ii * g_tailOffsetMult) * g_tailSpeed * speed * 0.2;
      fish.draw(fishPer);
    }

    // Draw Laser
    Log("--Draw Laser---------------------------------------");
    gl.enable(gl.BLEND);
    gl.blendFunc(gl.ONE, gl.ONE);
    gl.disable(gl.CULL_FACE);
    gl.depthMask(false);

    laser.drawPrep(laserConst);
    var c = 0.5 + (frameCount % 2) + 0.5;
    laserConst.colorMult = [c * 1, c * 0.1, c * 0.1, c];
    for (var ii = 0; ii < g_numSharks; ++ii) {
      var time = (clock + ii * g_sharkClockOffset) * 3;
      fast.matrix4.mul(
          m4t1,
          fast.matrix4.scaling(m4t0, [0.3 / 6, 0.3 / 6, 1000]),
              fast.matrix4.mul(
                  m4t2,
                  fast.matrix4.rotationY(m4t3, -Math.sin(time) * 0.025),
                  fast.matrix4.translation(m4t1, [0, 0.1, 0.5])));
      fast.matrix4.mul(
          g_beamWorldMats[ii],
          m4t1,
          g_sharkWorldMats[ii]);
      fast.matrix4.copy(world, g_beamWorldMats[ii]);
      fast.matrix4.mul(worldViewProjection, world, viewProjection);
      laser.draw(laserPer);
    }

    // Draw light flash
    Log("--Draw light flash---------------------------------------");
    gl.disable(gl.DEPTH_TEST);
//    flash.drawPrep(laserConst);
//    for (var ii = 0; ii < g_numSharks; ++ii) {
//      flash.draw(laserPer);
//    }

    // Draw Glass
    Log("--Draw Glass---------------------------------------");
    gl.enable(gl.BLEND);
    gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
    gl.enable(gl.CULL_FACE);
    gl.depthMask(true);
    gl.enable(gl.DEPTH_TEST);

    fast.matrix4.translation(world, [0, -5, 0]);
    fast.matrix4.mul(worldViewProjection, world, viewProjection);
    fast.matrix4.inverse(worldInverse, world);
    fast.matrix4.transpose(worldInverseTranspose, worldInverse);
    glass.drawPrep(glassConst);

    // Draw front of class
    if (!g_refract) {
      gl.cullFace(gl.FRONT);
      glass.draw(glassBackPer);
    }

    // Draw front of class
    gl.cullFace(gl.BACK);
    glass.draw(glassFrontPer);

    // Set the alpha to 255.
    gl.colorMask(false, false, false, true);
    gl.clearColor(0,0,0,1);
    gl.clear(gl.COLOR_BUFFER_BIT);

    // turn off logging after 1 frame.
    g_logGLCalls = false;

    if (!g_drawOnce) {
      g_requestId = requestAnimationFrame(render);
    }
  }
  render();
  return true;
}
</script>
</head>
<body>
<div id="info"><a href="http://threedlibrary.googlecode.com" target="_blank">tdl.js</a> - fishtank</div>
<div id="fpsContainer">
  <div class="fpsInner">
    <div id="fullscreen"></div>
    <div class="fps">fps: <span id="fps"></span></div>
    <div class="clear"></div>
  </div>
  <div>Number of Fish</div>
  <div id="setSetting0">1</div>
  <div id="setSetting1">10</div>
  <div id="setSetting2">100</div>
  <div id="setSetting3">1000</div>
  <div id="setSetting4">Sharks</div>
</div>
<div id="viewContainer">
<canvas id="canvas" width="1024" height="1024" style="width: 100%; height: 100%;"></canvas>
</div>
</body>
<!--
<script id="constVertexShader" type="text/something-not-javascript">
attribute vec4 position;
//attribute vec3 normal;
//attribute vec2 texCoord;
//varying vec2 v_texCoord;
//varying vec3 v_normal;
uniform mat4 worldViewProjection;
void main() {
  //v_texCoord = texCoord;
  //v_normal = normal;
  gl_Position = (worldViewProjection * position);
}
</script>
<script id="constFragmentShader" type="text/something-not-javascript">
precision mediump float;
//varying vec2 v_texCoord;
//varying vec3 v_normal;
void main() {
//  gl_FragColor = vec4(vec3(v_texCoord, 1) + v_normal, 1);
  gl_FragColor = vec4(1, 1, 0, 1);
}
</script>
-->
<script id="laserVertexShader" type="text/something-not-javascript">
attribute vec4 position;
attribute vec2 texCoord;
varying vec2 v_texCoord;
uniform mat4 worldViewProjection;
void main() {
  v_texCoord = texCoord;
  gl_Position = (worldViewProjection * position);
}
</script>
<script id="laserFragmentShader" type="text/something-not-javascript">
precision mediump float;

varying vec2 v_texCoord;
uniform vec4 colorMult;
uniform sampler2D colorMap;
void main() {
  gl_FragColor = texture2D(colorMap, v_texCoord) * colorMult;
}
</script>
<!-- ===[ Fish Shader ]============================================== -->
<script id="fishVertexShader" type="text/something-not-javascript">
uniform mat4 worldViewProjection;
uniform vec3 lightWorldPos;
uniform mat4 world;
uniform mat4 viewInverse;
uniform mat4 worldInverseTranspose;
uniform float time;
attribute vec4 position;
attribute vec3 normal;
attribute vec2 texCoord;
attribute vec3 tangent;
attribute vec3 binormal;
varying vec4 v_position;
varying vec2 v_texCoord;
varying vec3 v_tangent;
varying vec3 v_binormal;
varying vec3 v_normal;
varying vec3 v_surfaceToLight;
varying vec3 v_surfaceToView;
void main() {
  v_texCoord = texCoord;
  float mult = position.z > 0.0 ? (position.z / 10.0) : (-position.z / 20.0);  // 10 = radius.
  float s = sin(time + mult * 3.0);
  float a = sign(s);
  float offset = pow(mult, 2.0) * s * 20.0;
  v_position = (
      worldViewProjection *
      (position +
       vec4(offset, 0, 0, 0)));
  v_normal = (worldInverseTranspose * vec4(normal, 0)).xyz;
  v_surfaceToLight = lightWorldPos - (world * position).xyz;
  v_surfaceToView = (viewInverse[3] - (world * position)).xyz;
  v_binormal = (worldInverseTranspose * vec4(binormal, 0)).xyz;
  v_tangent = (worldInverseTranspose * vec4(tangent, 0)).xyz;
  gl_Position = v_position;
}

</script>
<script id="fishFragmentShader" type="text/something-not-javascript">
#ifdef GL_FRAGMENT_PRECISION_HIGH
  precision highp float;
#else
  precision mediump float;
#endif
uniform vec4 lightColor;
varying vec4 v_position;
varying vec2 v_texCoord;
varying vec3 v_tangent;
varying vec3 v_binormal;
varying vec3 v_normal;
varying vec3 v_surfaceToLight;
varying vec3 v_surfaceToView;

uniform vec4 emissive;
uniform vec4 colorMult;
uniform sampler2D diffuseSampler;
uniform vec4 specular;
uniform sampler2D bumpSampler;
uniform samplerCube skybox;
uniform float shininess;
uniform float specularFactor;

vec4 lit(float l ,float h, float m) {
  return vec4(1.0,
              max(l, 0.0),
              (l > 0.0) ? pow(max(0.0, h), m) : 0.0,
              1.0);
}
void main() {
  vec4 diffuse = texture2D(diffuseSampler, v_texCoord);
  mat3 tangentToWorld = mat3(v_tangent,
                             v_binormal,
                             v_normal);
  vec3 tangentNormal = texture2D(bumpSampler, v_texCoord.xy).xyz -
                                 vec3(0.5, 0.5, 0.5);
  tangentNormal = normalize(tangentNormal + vec3(0, 0, 2));
  vec3 normal = (tangentToWorld * tangentNormal);
  normal = normalize(normal);
  vec3 surfaceToLight = normalize(v_surfaceToLight);
  vec3 surfaceToView = normalize(v_surfaceToView);
  vec4 skyColor = textureCube(skybox, -reflect(surfaceToView, normal));
  vec3 halfVector = normalize(surfaceToLight + surfaceToView);
  vec4 litR = lit(dot(normal, surfaceToLight),
                    dot(normal, halfVector), shininess);
  gl_FragColor = vec4((emissive +
  lightColor * colorMult * ((diffuse * vec4(0.0, 0.0, 0.0, 0.0) +
                 skyColor * vec4(1.0, 1.0, 1.0, 1.0)) /* * litR.y */ +
                        + specular * litR.z * specularFactor)).rgb,
      diffuse.a);
}
</script>
<!-- ===[ Shark Shader ]============================================== -->
<script id="sharkVertexShader" type="text/something-not-javascript">
uniform mat4 worldViewProjection;
uniform vec3 lightWorldPos;
uniform mat4 world;
uniform mat4 viewInverse;
uniform mat4 worldInverseTranspose;
uniform float time;
attribute vec4 position;
attribute vec3 normal;
attribute vec2 texCoord;
attribute vec3 tangent;
attribute vec3 binormal;
varying vec4 v_position;
varying vec2 v_texCoord;
varying vec3 v_normal;
varying vec3 v_surfaceToLight;
varying vec3 v_surfaceToView;
void main() {
  v_texCoord = texCoord;
  float mult = position.z > 0.0 ? (position.z / 10.0) : (-position.z / 20.0);  // 10 = radius.
  float s = sin(time + mult * 3.0);
  float a = sign(s);
  float offset = pow(mult, 2.0) * s * 20.0;
  v_position = (
      worldViewProjection *
      (position +
       vec4(offset, 0, 0, 0)));
  v_normal = (worldInverseTranspose * vec4(normal, 0)).xyz;
  v_surfaceToLight = lightWorldPos - (world * position).xyz;
  v_surfaceToView = (viewInverse[3] - (world * position)).xyz;
  gl_Position = v_position;
}

</script>
<script id="sharkFragmentShader" type="text/something-not-javascript">
#ifdef GL_FRAGMENT_PRECISION_HIGH
  precision highp float;
#else
  precision mediump float;
#endif
uniform vec4 lightColor;
varying vec4 v_position;
varying vec2 v_texCoord;
varying vec3 v_normal;
varying vec3 v_surfaceToLight;
varying vec3 v_surfaceToView;

uniform vec4 specular;
uniform float shininess;
uniform float specularFactor;
uniform sampler2D diffuseSampler;

vec4 lit(float l ,float h, float m) {
  return vec4(1.0,
              max(l, 0.0),
              (l > 0.0) ? pow(max(0.0, h), m) : 0.0,
              1.0);
}
void main() {
  vec3 normal = normalize(v_normal);
  vec4 diffuse = texture2D(
      diffuseSampler,
      vec2(v_texCoord.x, normal.y * 0.5 + 0.7));
  vec3 surfaceToLight = normalize(v_surfaceToLight);
  vec3 surfaceToView = normalize(v_surfaceToView);
  vec3 halfVector = normalize(surfaceToLight + surfaceToView);
  vec4 litR = lit(dot(normal, surfaceToLight),
                    dot(normal, halfVector), shininess);
  gl_FragColor = vec4((
                       lightColor * (diffuse * litR.y +
                                     specular * litR.z * specularFactor)).rgb,
                      diffuse.a) * 0.3 + diffuse * 0.7;
}
</script>
<!-- ===[ Glass Shader ]============================================== -->
<script id="glassVertexShader" type="text/something-not-javascript">
uniform mat4 worldViewProjection;
uniform mat4 world;
uniform mat4 viewInverse;
uniform mat4 worldInverseTranspose;
uniform float normMult;
attribute vec4 position;
attribute vec3 normal;
attribute vec2 texCoord;
attribute vec3 tangent;
attribute vec3 binormal;
varying vec4 v_position;
varying vec2 v_texCoord;
varying vec3 v_tangent;
varying vec3 v_binormal;
varying vec3 v_normal;
varying vec3 v_surfaceToView;
void main() {
  v_texCoord = texCoord;
  v_position = worldViewProjection * position;
  v_normal = (worldInverseTranspose * vec4(normal, 0)).xyz;
  v_surfaceToView = (viewInverse[3] - (world * position)).xyz;
  v_binormal = (worldInverseTranspose * vec4(binormal, 0)).xyz;
  v_tangent = (worldInverseTranspose * vec4(tangent, 0)).xyz;
  gl_Position = v_position;
}

</script>
<script id="glassFragmentShader" type="text/something-not-javascript">
#ifdef GL_FRAGMENT_PRECISION_HIGH
  precision highp float;
#else
  precision mediump float;
#endif
varying vec4 v_position;
varying vec2 v_texCoord;
varying vec3 v_tangent;
varying vec3 v_binormal;
varying vec3 v_normal;
varying vec3 v_surfaceToView;

uniform sampler2D bumpSampler;
uniform samplerCube skybox;

vec4 lerp(vec4 a, vec4 b, float mix) {
  return a + (b - a) * vec4(mix, mix, mix, mix);
}
void main() {
  mat3 tangentToWorld = mat3(v_tangent,
                             v_binormal,
                             v_normal);
  vec3 tangentNormal = texture2D(bumpSampler, v_texCoord.xy).xyz -
                                 vec3(0.5, 0.5, 0.5);
  tangentNormal = normalize(tangentNormal + vec3(0, 0, 20));
  vec3 normal = (tangentToWorld * tangentNormal);
  normal = normalize(normal);
  vec3 surfaceToView = normalize(v_surfaceToView);
  vec4 skyColor = textureCube(skybox, -reflect(surfaceToView, normal));
  float waterAmount = 0.2;
  vec4 waterColor = vec4(0,0.9,1,1);
  float fudgeAmount = 1.1;
  vec3 fudge = skyColor.rgb * vec3(fudgeAmount, fudgeAmount, fudgeAmount);
  float bright = min(1.0, fudge.r * fudge.g * fudge.b);
  vec4 reflectColor = lerp(vec4(skyColor.rgb, bright), waterColor, waterAmount);
  float r = abs(dot(surfaceToView, normal));
  gl_FragColor = lerp(skyColor, reflectColor, r + 0.3);
}
</script>
<!-- ===[ Normal Map Shader ]============================================== -->
<script id="normalMapVertexShader" type="text/something-not-javascript">
uniform mat4 worldViewProjection;
uniform vec3 lightWorldPos;
uniform mat4 world;
uniform mat4 viewInverse;
uniform mat4 worldInverseTranspose;
attribute vec4 position;
attribute vec3 normal;
attribute vec2 texCoord;
attribute vec3 tangent;
attribute vec3 binormal;
varying vec4 v_position;
varying vec2 v_texCoord;
varying vec3 v_tangent;
varying vec3 v_binormal;
varying vec3 v_normal;
varying vec3 v_surfaceToLight;
varying vec3 v_surfaceToView;
void main() {
  v_texCoord = texCoord;
  v_position = (worldViewProjection * position);
  v_normal = (worldInverseTranspose * vec4(normal, 0)).xyz;
  v_surfaceToLight = lightWorldPos - (world * position).xyz;
  v_surfaceToView = (viewInverse[3] - (world * position)).xyz;
  v_binormal = (worldInverseTranspose * vec4(binormal, 0)).xyz;
  v_tangent = (worldInverseTranspose * vec4(tangent, 0)).xyz;
  gl_Position = v_position;
}

</script>
<script id="normalMapFragmentShader" type="text/something-not-javascript">
#ifdef GL_FRAGMENT_PRECISION_HIGH
  precision highp float;
#else
  precision mediump float;
#endif
uniform vec4 lightColor;
varying vec4 v_position;
varying vec2 v_texCoord;
varying vec3 v_tangent;
varying vec3 v_binormal;
varying vec3 v_normal;
varying vec3 v_surfaceToLight;
varying vec3 v_surfaceToView;

uniform vec4 emissive;
uniform vec4 ambient;
uniform sampler2D diffuseSampler;
uniform vec4 specular;
uniform sampler2D bumpSampler;
uniform float shininess;
uniform float specularFactor;

vec4 lit(float l ,float h, float m) {
  return vec4(1.0,
              max(l, 0.0),
              (l > 0.0) ? pow(max(0.0, h), m) : 0.0,
              1.0);
}
void main() {
  vec4 diffuse = texture2D(diffuseSampler, v_texCoord);
  mat3 tangentToWorld = mat3(v_tangent,
                             v_binormal,
                             v_normal);
  vec3 tangentNormal = texture2D(bumpSampler, v_texCoord.xy).xyz -
                                 vec3(0.5, 0.5, 0.5);
  vec3 normal = (tangentToWorld * tangentNormal);
  normal = normalize(normal);
  vec3 surfaceToLight = normalize(v_surfaceToLight);
  vec3 surfaceToView = normalize(v_surfaceToView);
  vec3 halfVector = normalize(surfaceToLight + surfaceToView);
  vec4 litR = lit(dot(normal, surfaceToLight),
                    dot(normal, halfVector), shininess);
  gl_FragColor = vec4((emissive +
  lightColor * (ambient * diffuse + diffuse * litR.y +
                        + specular * litR.z * specularFactor)).rgb,
      diffuse.a);
}
</script>
<!-- ===[ SkyBox ]============================================== -->
<script id="refractSkyboxVertexShader" type="text/something-not-javascript">
attribute vec4 position;
varying vec4 v_position;

void main() {
  v_position = position;
  gl_Position = position;
}
</script>
<script id="refractSkyboxFragmentShader" type="text/something-not-javascript">
#ifdef GL_FRAGMENT_PRECISION_HIGH
  precision highp float;
#else
  precision mediump float;
#endif
uniform samplerCube skybox;
uniform mat4 viewProjectionInverse;
uniform mat4 viewInverse;
uniform float tankRadiusSquared;
uniform float tankRadiusInverse;
uniform float tankRefractionRatio;
varying vec4 v_position;

void main() {
  vec4 fragment = viewProjectionInverse * v_position;
  fragment /= fragment.w;
  vec3 viewPosition = viewInverse[3].xyz;
  vec3 viewDirection = normalize(fragment.xyz - viewPosition);

  // Trace ray from camera to tank cylinder. Start with an infinite cylinder.
  float a = dot(viewDirection.xz, viewDirection.xz);
  float b = 2.0 * dot(viewPosition.xz, viewDirection.xz);
  float c = dot(viewPosition.xz, viewPosition.xz) - tankRadiusSquared;
  float discriminant = b * b - 4.0 * a * c;

  // If the discrimant is negative then the ray does not collide with the tank.
  if (discriminant < 0.0) {
    gl_FragColor = textureCube(skybox, viewDirection);
  } else {
    // Calculate the near and far collision times and pick the closest.
    float sqrtDiscrimant = sqrt(discriminant);
    float inverse2a = 1.0 / (2.0 * a);
    float tn = (-b - sqrtDiscrimant) * inverse2a;
    vec3 nearPosition = viewPosition + tn * viewDirection;

    // The surface normal for the sphere is the intersecting position normalized
    // by dividing by the radius.
    vec3 nearSurfaceNormal = normalize(vec3(nearPosition.x, 0, nearPosition.z));
    vec3 nearRefractedDirection = refract(viewDirection, nearSurfaceNormal, 1.0 / tankRefractionRatio);

    // Trace ray from near intersection to tank cylinder.
    a = dot(nearRefractedDirection.xz, nearRefractedDirection.xz);
    b = 2.0 * dot(nearPosition.xz, nearRefractedDirection.xz);
    c = dot(nearPosition.xz, nearPosition.xz) - tankRadiusSquared;
    discriminant = b * b - 4.0 * a * c;

    // Assume discriminant is not negative because we should have been tracing
    // through the cylinder. Calculate near and far collision times and pick
    // far one. Near one should be approximately zero. TODO: optimization here?
    sqrtDiscrimant = sqrt(discriminant);
    inverse2a = 1.0 / (2.0 * a);
    float tf = (-b + sqrtDiscrimant) * inverse2a;
    vec3 farPosition = nearPosition + tf * nearRefractedDirection;

    // The surface normal for the sphere is the intersecting position normalized
    // by dividing by the radius.
    vec3 farSurfaceNormal = normalize(vec3(farPosition.x, 0, farPosition.z));
    vec3 farRefractedDirection = refract(nearRefractedDirection, -farSurfaceNormal, tankRefractionRatio);

    vec4 externalColor = textureCube(skybox, farRefractedDirection);

    vec4 murkColor = vec4(0.1, 0.11, 0.1, 1.0);
    float murkVisibilityDistance = 100.0;

    float murkiness = min(1.0, tf / murkVisibilityDistance);

    gl_FragColor = mix(externalColor, murkColor, murkiness);
  }
}
</script>
<script id="skyboxVertexShader" type="text/something-not-javascript">
attribute vec4 position;
varying vec4 v_position;
void main() {
  v_position = position;
  gl_Position = position;
}
</script>
<script id="skyboxFragmentShader" type="text/something-not-javascript">
#ifdef GL_FRAGMENT_PRECISION_HIGH
  precision highp float;
#else
  precision mediump float;
#endif
uniform samplerCube skybox;
uniform mat4 viewDirectionProjectionInverse;
varying vec4 v_position;
void main() {
  vec4 t = viewDirectionProjectionInverse * v_position;
  gl_FragColor = textureCube(
      skybox,
      normalize(t.xyz / t.w));
}
</script>
</html>


